
//变量非空值判断，返回Boolean
const isValue = (value) => {
	return (undefined !== value && null !== value && "" !== value) ? true : false
}

//已定义变量判断，返回Boolean
const isDefined = (value) => {
	return (undefined !== value && null !== value)? true : false
}

//非零有效值判断，返回Boolean
const isNonzero = (value) => {
	return value && value != "0" ? true : false
}

//返回变量类型，首字母大写
const typeAsk = (data) => {
	let str = Object.prototype.toString.call(data);
	return str.substring(8,str.length-1);
	// str = str.match(/\[object (\S*)\]/)[1];
}

//类型判断，字符串
const isString = (data) => {
	return typeof(data) == "string"
}

//类型判断，数值，expand加强验证
const isNumber = (data, expand=false) => {
	if(expand){//能转换为数字的都为true
		return isValue(data) && !isNaN(Number(data))
	}else{
		return typeof(data) == "number"
	}
}

//类型判断，字符串或数值
const isStringOrNumber = (data) => {
	let type = typeof(data);
	return type == "string" || type == "number"
}

//类型判断，布尔
const isBoolean = (data) => {
	return typeof(data) == "boolean"
}

//类型判断，日期，expand加强验证
const isDate = (data, expand=false) => {
	if(expand){//能转换为日期的都为true
		return data && !/Invalid|NaN/.test(new Date(data).toString())
	}else{
		return typeAsk(data) == "Date"
	}
}

//类型判断，symbol
const isSymbol = (data) => {
	return typeAsk(data) == "Symbol"
}

//类型判断，空值
const isNull = (data) => {
	return typeAsk(data) == "Null"
}

//类型判断，未定义
const isUndefined = (data) => {
	return typeof(data) == "undefined"
}

//类型判断，对象
const isObject = (data) => {
	return typeAsk(data) == "Object"
}

//类型判断，数组
const isArray = (data) => {
	return Array.isArray ? Array.isArray(data) : typeAsk(data) == "Array"
}

//类型判断，函数
const isFunction = (data) => {
	return typeof(data) == "function"
}

//类型判断，对象或数组
const isObjectOrArray = (data) => {
	let type = typeAsk(data);
	return type == "Object" || type == "Array"
}

//判断是否为空对象，空数组，或空值(数字0不是空值)
const isEmpty = (data) => {
	let type = typeAsk(data);
	if(type == "Object"){
		return Object.keys(data).length ==0
	}else if(type == "Array"){
		return data.length ==0
	}else{
		return !isValue(data)
	}
}

//类型判断，字符串是颜色串
const isColor = (data) => {
	if(!data||!isString(data)){
		return false
	}
	if( (data.startsWith("#") && (data.length==4||data.length==7)) ||
		(data.startsWith("rgb") && data.endsWith(")")) ||
		(data.startsWith("linear-gradient(") && data.endsWith(")"))
	){
		return true
	}
	return false
}

/*
两个变量相等对比，返回Boolean
非严格模式下
	null 等于字符串 null
	undefined 等于字符串 undefined
	date 等于等值数字串或字符串
*/
const isEqual = (val1, val2, strict=false) => {
	let type1 = typeAsk(val1);
	let type2 = typeAsk(val2);
	val1 = type1 == "Date" ? val1.getTime() : val1;
	val2 = type2 == "Date" ? val2.getTime() : val2;
	if(type1 == "Number" || type2 == "Number"){//其中一项为数字，转换为数字对比
		let n1 = Number(val1), n2 = Number(val2);
		let eq = ( n1 - n2 < Number.EPSILON ) && ( n2 - n1 < Number.EPSILON )
		return strict ? (type1 == type2) && eq : eq
	}else{
		return strict ? (type1 == type2) && (val1 == val2) : val1 == val2
	}
}

//返回变量长度，字符串、数组、对象、数字
const lengthOf = (data, options={}) => {
	let { CN2=false, type="normal" } = options;
	if(!isValue(data)){
		return 0
	}
	if(type == "normal"){
		switch(typeAsk(data)){
			case "String":
				if(CN2){
					return _getCNLength(data)
				}else{
					return data.length
				}
			case "Number":
				return String(data).length
			case "Array":
				return data.length
			case "Object":
				return Object.keys(data).length
			default:
				console.error("function: lengthOf, 参数类型错误", {
					"参数1(字符串/数字/数组/对象)": typeAsk(data)
				});
				return -1
		}
	}else if(type == "decimal" && isNumber(data, true)){
		let split = String(data).split(".");
		if(split[1]){
			return split[1].length
		}
		return 0
	}
	return 0
}

const _getCNLength = (data) => {
	return data.replace(/[^\x00-\xff]/g,"01").length;
}

/* 节流防抖
丢弃约定时间内多次触发的执行，在约定时间内只执行一次。
运行该功能返回一个可高频率反复调用的函数，可传入参数
options:{
	wait:"间隔时间"
	first:"首次立即执行" (默认true)
	last:"最后一次补充执行" (默认true)
	interval:"单位时间内至少执行一次" (默认true，false时为防抖，仅末次执行)
}
*/
const throttle = (callback, options={}) => {
	if(!isFunction(callback)){
		console.error("function: locker, 参数类型错误", {
			"参数1(函数)": typeAsk(callback)
		});
		return function(){}
	}
	let {first=true, last=true, interval=true, wait=100, type} = options;
	if(type == "last"){//末次运行，例如滚动条
		first = false;
		interval = false;
	}else if(type == "reject"){//间隔时间内只能运行一次，重复运行则抛弃。例如重复点击按钮
		last = false;
	}
	let timer, next;
	if(interval){//需定时执行
		return function(...param) {
			let now = Date.now();
			if(!next){//第一次
				next = now + wait;
				if(first){
					callback(...param);
				}
			}else if(now >= next){
				next = now + wait;
				callback(...param);
			}else if(last){//补最后一次执行，使用定时器
				if(timer){
					clearTimeout(timer);
					timer = null;
				}
				timer = setTimeout(() => {
					loopRun()
				}, wait)
			}
		}
	}else{//无需定时执行，只执行最后一次
		return function(...param){
			if(timer){
				clearTimeout(timer);
				timer = null;
			}
			timer = setTimeout(()=>{
				callback(...param)
			}, wait)
		}
	}
}

/* 锁柜
给callback增加锁设置，重复调用locker(callback, options)，在上锁状态时callback不会执行。在第一次调用locker后，函数才会具有锁状态
options:{//锁钥，object引用，仅第一次赋予。不传入则成为封闭锁，需使用callback._lock()更改锁状态。
	value:"锁状态" (默认false，true时表示上锁)
	msg:"提示信息" (运行callback时如果已上锁则弹出提示，非必须)
	autolock:"callback运行后自动上锁" (默认false)
}
callback._lock() 给函数上锁
callback._unlock() 给函数解锁
callback._changeLock(options) 给函数换锁
*/
const locker = (callback, options={}) => {
	if(!callback._lock){
		callback._key = options;
		callback._lock = function(){
			this._key.value = true
		}
		callback._unlock = function(){
			this._key.value = false
		}
		callback._changeLock = function(obj){
			this._key = obj
		}
	}
	if(!callback._key.value){
		callback();
		if(callback._key.autolock){
			callback._lock()
		}
	}else if(callback._key.msg){
		msg(callback._key.msg)
	}
}

/*
格式化日期
options:{
	format:"返回格式" (默认YYYY-MM-DD，YYYY完整年、YY小写年、MM完整月、M小写月、DD完整天、D小写天、Q季度、W星期、hh完整时、h小写时、mm完整分、m小写分、ss完整秒、s小写秒)
	week:"星期显示值" (传入键名0到7的对象,0为周日)
	quarter:"季度显示值" (传入键名1到4的对象)
}
*/
const dateFormat = (date, options) => {
	if(isObject(date) && !options){
		options = date;
		date = new Date()
	}
	if(!isValue(date)){
		date = new Date()
	}
	let {format="YYYY-MM-DD", week={}, quarter={}} = options ||{};
	if(!isDate(date)){
		let check = true;
		if(!!Number(date)){//可转换数字的，转换数字后再转日期
			date = new Date(Number(date));
		}else{//其他类型直接转换
			date = new Date(date);
		}
		if(!date.getTime()){
			console.error("function: dateFormat, 参数错误", {
				"参数1": date
			});
			return ""
		}
	}
	let str = format;
	let wk = Object.assign({"0":'日', "1":'一', "2":'二', "3":'三', "4":'四', "5":'五', "6":'六'}, week);
	let qt = Object.assign({"1":'一', "2":'二', "3":'三', "4":'四'}, quarter);
	let Y = date.getFullYear();
	let y = date.getYear();
	let M = date.getMonth();
	let D = date.getDate();
	let W = date.getDay();
	let h = date.getHours();
	let m = date.getMinutes();
	let s = date.getSeconds();
	str = str.replace(/YYYY/, Y.toString());
	str = str.replace(/YY/, (y % 100) > 9 ? (y % 100).toString() : '0' + (y % 100));
	str = str.replace(/Q/g, qt[parseInt(M/3+1).toString()]);
	str = str.replace(/MM/, (M + 1) > 9 ? (M + 1).toString() : '0' + (M + 1));
	str = str.replace(/M/g, (M + 1));
	str = str.replace(/W/g, wk[String(W)]);
	str = str.replace(/DD/, D > 9 ? D.toString() : '0' + D);
	str = str.replace(/D/g, D);
	str = str.replace(/hh/, h > 9 ? h.toString() : '0' + h);
	str = str.replace(/h/g, h);
	str = str.replace(/mm/, m > 9 ? m.toString() : '0' + m);
	str = str.replace(/m/g, m);
	str = str.replace(/ss/, s > 9 ? s.toString() : '0' + s);
	str = str.replace(/s/g, s);
	return str
}

/*
返回日期点
options:{
	year:"增减年"
	month:"增减月"
	day:"增减天"
	format:"返回格式" (默认timestamp时间戳、fullDate|date|long长日期同YYYY-MM-DD、halfDate|short短日期同YY-M-D、dateTime完整日期时间、自定义格式)
	withTime:"是否包含时间" (默认false，时间部分将清零)
	overFix:"修复溢出"（默认true，只增减年月的情况使用，由于每月天数不定，计算结束后可能跳至下月，开启后抹去溢出，倒退到上月末）
}
*/
const datePoint = (target, options) => {
	if(isObject(target) && !options){
		options = target;
		target = new Date()
	}
	let { year=0,month=0,day=0,format="timestamp",withTime=false,overFix=true} = options ||{};
	let date = "";
	if(isValue(target)){
		if(!isDate(target)){
			date = new Date(target);
		}else{
			date = new Date(target.valueOf());
		}
	}else{
		date = new Date();
	}
	let cd = date.getDate();
	date.setFullYear(date.getFullYear() + year);
	date.setMonth(date.getMonth() + month);
	date.setDate(date.getDate() + day);
	if(overFix && day==0 && date.getDate()!=cd){
		date.setDate(0)
	}
	if(!withTime){
		date.setHours(0);
		date.setMinutes(0);
		date.setSeconds(0);
		date.setMilliseconds(0);
	}
	let formatStr = "";
	if(format == "timestamp" || format == "time" || format == "stamp"){
		return date.getTime();
	}else if(format == "fullDate" || format == "date" || format == "long"){
		formatStr = 'YYYY-MM-DD'
	}else if(format == "halfDate" || format == "short"){
		formatStr = 'YY-M-D'
	}else if(format == "dateTime"){
		formatStr = 'YYYY-MM-DD hh:mm:ss'
	}else if(isString(format)){
		formatStr = format
	}else{
		return date.getTime();
	}
	return dateFormat(date, {format: formatStr});
}

/*
得到两个日期差值
options:{
	format:"返回类型" (默认day相差天数、month相差月数、year相差年数、timestamp时间戳差值)
	sign:"是否带正负符号" (默认false)
}
*/
const dateDiffer = (target1, target2, options) => {
	if(isObject(target2) && !options){
		options = target2;
		target2 = new Date()
	}
	let { format="day", sign=false } = options ||{};
	let date1 = target1;
	let date2 = target2;
	if(isValue(target1)){
		if(!isDate(target1)){
			date1 = new Date(target1);
		}
	}else{
		date1 = new Date();
	}
	if(isValue(target2)){
		if(!isDate(target2)){
			date2 = new Date(target2);
		}
	}else{
		date2 = new Date();
	}
	let Y1 = date1.getFullYear();
	let Y2 = date2.getFullYear();
	let m1 = date1.getMonth();
	let m2 = date2.getMonth();
	let result = "";
	
	switch(format){
		case "day":
			result = parseInt((date2.getTime() - date1.getTime())/(1000*3600*24));
			break;
		case "month":
			result = parseInt((Y2 - Y1) * 12 + m2 - m1);
			break;
		case "year":
			result = Y2 - Y1;
			break;
		default:
			result = date2.getTime() - date1.getTime();
	}
	return sign ? result : Math.abs(result);
}

/*
时间格式化，约束为时间数值或标准格式时间字符串，时、分、秒、毫秒，需要标注传入的时间基本单位，传入时间基本单位上限为天
options:{
	format:"返回格式" (默认hh:mm:ss。P，上下午，默认AM/PM。ms，毫秒)
	meridiem:"上下午显示值" (传入键名0到1的对象，0为上午，1为下午)
	unit:"传入时间的最小单位" (用于转换为ms，默认ms。可取值d、h、m、s、ms)
}
*/
const timeFormat = (time, options) => {
	if(isObject(time) && !options){
		options = time;
		time = null;
	}
	if(!options){
		options={}
	}
	if(!isValue(time)){
		let now = new Date();
		time = now.getHours()*1000*3600 + now.getMinutes()*1000*60 + now.getSeconds()*1000 + now.getMilliseconds();
		options.unit = "ms";
	}
	let {format="hh:mm:ss", meridiem={}, unit="ms"} = options;
	if(!unit){
		console.error("function: timeFormat, 参数错误", {
			"参数2.unit(h/m/s/ms)": unit
		});
		return ""
	}
	let timeLength={
		"day":1,
		"d":1,
		"hour":2,
		"h":2,
		"minute":3,
		"m":3,
		"second":4,
		"s":4,
		"millisecond":5,
		"ms":5
	}
	//如果时间为字符串，需转换为数值
	if(!isNumber(time, true)){
		if(isString(time)){
			let timeArr = time.match(/[0-9]+/g) ||[];
			if(!timeLength[unit]){
				console.error("function: timeFormat, 参数错误", {
					"参数2.unit(d/h/m/s/ms)": unit
				});
				return ""
			}
			if(timeArr.length <1 || timeArr.length >timeLength[unit]){
				console.error("function: timeFormat, 参数错误", {
					"参数1": time
				});
				return ""
			}else{
				let hmsArr, start;
				if(timeArr.length <timeLength[unit]){//没有天数，初始值为0
					start = 0;
					time = 0;
				}else{//有天数，设置初始值为天数转换
					start = 1;
					switch(unit){
						case "day":
						case "d":
							time = timeArr[0];
							break;
						case "hour":
						case "h":
							time = timeArr[0] *24;
							break;
						case "minute":
						case "m":
							time = timeArr[0] *60*24;
							break;
						case "second":
						case "s":
							time = timeArr[0] *60*60*24;
							break;
						case "millisecond":
						case "ms":
							time = timeArr[0] *1000*60*60*24;
					}
				}
				if(unit == "millisecond" || unit == "ms"){//最小单位为毫秒，进位不一致，单独处理
					hmsArr = timeArr.slice(start, timeArr.length-1);
					time += timeArr[timeArr.length-1];//最后一项为标准毫秒
					for(let i=hmsArr.length-1; i>=0; i--){
						time += hmsArr[i] *Math.pow(60, hmsArr.length-1-i) *1000;
					}
				}else{
					hmsArr = timeArr.slice(start);
					for(let i=hmsArr.length-1; i>=0; i--){
						time += hmsArr[i] *Math.pow(60, hmsArr.length-1-i);
					}
				}
			}
		}else{
			console.error("function: timeFormat, 参数类型错误", {
				"参数1(数字/字符串)": typeAsk(time)
			});
			return ""
		}
	}else{
		time = Number(time);
	}
	//时间数值统一转换为毫秒
	if(unit == "hour" || unit == "h"){
		time *= 1000*3600
	}else if(unit == "minute" || unit == "m"){
		time *= 1000*60
	}else if(unit == "second" || unit == "s"){
		time *= 1000
	}
	let str = format;
	let APM = Object.assign({"0":'AM', "1":'PM'}, meridiem);
	if(isArray(str.match(/Y+/g))){//年。存在天数以上单位，需对结果值进行二次处理
		let Y = parseInt(time/(1000*3600*24*365.25));
		str = str.replace(/Y/g, Y);
		time -= Math.ceil(Y*365.25)*1000*3600*24;
	}
	if(isArray(str.match(/M+/g))){//月。存在天数以上单位，需对结果值进行二次处理
		let M = parseInt(time/(1000*3600*24*30.4375));
		str = str.replace(/M/g, M);
		time -= Math.ceil(M*30.4375)*1000*3600*24;
	}
	let D = parseInt(time/(1000*3600*24));
	let H = parseInt(time%(1000*3600*24) /(1000*3600));
	let h = H>12? H-12 : H;
	let m = parseInt(time%(1000*3600) /(1000*60));
	let s = parseInt(time%(1000*60) /1000);
	let ms = time%1000;
	let P = time >= 1000*3600*12 ? APM["1"] : APM["0"];
	str = str.replace(/D/g, D);
	//存在上下午标志则使用半天类型
	if(isArray(str.match(/P+/g))){
		str = str.replace(/hh/g, h > 9 ? h.toString() : '0' + h);
		str = str.replace(/h/g, h);
		str = str.replace(/P/g, P);
	}else{
		str = str.replace(/hh/g, H > 9 ? H.toString() : '0' + H);
		str = str.replace(/h/g, H);
	}
	str = str.replace(/ms/g, ms);
	str = str.replace(/mm/g, m > 9 ? m.toString() : '0' + m);
	str = str.replace(/m/g, m);
	str = str.replace(/ss/g, s > 9 ? s.toString() : '0' + s);
	str = str.replace(/s/g, s);
	return str
}


/*
返回两个时间差值，数字单位同传入参数单位
options:{
	sign:"是否带正负符号" (默认false)
}
*/
const timeDiffer = (target1, target2, option={}) => {
	let { sign=false } = option;
	if(!isString(target1) || !isString(target2)){
		console.error("function: timeDiffer, 参数类型错误", {
			"参数1(字符串)": typeAsk(target1),
			"参数2(字符串)": typeAsk(target2)
		});
		return 0
	}
	
	let reg = new RegExp("[0-9]+","g");
	let t1_arr = target1.match(reg);
	let t2_arr = target2.match(reg);
	let t1_val = 0;
	let t2_val = 0;
	if(t1_arr.length != t2_arr.length){
		console.error("function: timeDiffer, 比较目标长度不等", {
			"参数1": target1,
			"参数2": target2
		});
		return 0
	}
	for(let i=0; i<t1_arr.length; i++){
		t1_val += parseInt(t1_arr[i]) * Math.pow(60, t1_arr.length - 1 - i);
	}
	for(let i=t2_arr.length -1; i>=0; i--){
		t2_val += parseInt(t2_arr[i]) * Math.pow(60, t1_arr.length - 1 - i);
	}
	// console.log(`转换为${t1_val}比对${t2_val}`);
	return sign ? t2_val - t1_val : Math.abs(t2_val - t1_val)
}

/*
数据校验，返回校验对象{res:Boolean, msg:Array}
配置项keys
	正则表达式,返回正则运算结果,/g
	关键词字符串,从reg中匹配对应正则表达式
	关键词拼接字符串,从reg中匹配多个对应正则表达式
	关键词数组,从reg中匹配多个对应正则表达式
	对象数组{rule:"", value:[参数1,参数2]}，从reg中匹配多个对应正则表达式，传入附加参数

options{
	or:"是否或匹配"（默认false）
	allErr:"是否执行全部校验项，提取所有错误信息"（默认false）
	notRequired:"非必填"（用于表单校验与必填规则分离，默认使用时为必填）
	msg:"是否带错误信息"（默认false）
}
*/
const regCheck = (data, keys, options={}) => {
	let {or=false, allErr=false, notRequired=false, msg=false} = options;
	if(!keys){
		return msg||allErr? {res:true} : true
	}
	let rules;
	let resMsg=[];//所有错误信息
	switch(typeAsk(keys)){
		case "String":
			rules = keys.match(/\w+/g);
			break;
		case "Array":
			rules = keys;
			break;
		case "Object":
			rules = [keys];
			break;
		case "RegExp":
			return data.match(keys)
	}
	if(!or){//and关联
		let checkPass = true;
		for(let item of rules){
			if(isObject(item)){//对象，带参数
				if(!item.rule || (notRequired && item.rule != "required" && !isValue(data)) ){//无规则或数据为空(不是必填校验时)跳过
					continue
				}
				let checkObj = reg[item.rule](data, item.value);
				if(!checkObj.res){//校验失败
					if(!allErr){//无需校验所有错误，立即返回当前错误
						return msg||allErr? checkObj : false
					}
					checkPass = false;
					resMsg.push(checkObj.msg);
				}
			}else if(reg[item]){//非对象
				if(!item || (notRequired && item != "required" && !isValue(data)) ){//无规则或数据为空(不是必填校验时)跳过
					continue
				}
				let checkObj = reg[item](data);
				if(!checkObj.res){//校验失败
					if(!allErr){//无需校验所有错误，立即返回当前错误
						return msg||allErr? checkObj : false
					}
					checkPass = false;
					resMsg.push(checkObj.msg);
				}
			}else{
				//错误的规则跳过，当做不存在
				console.warn("function: regCheck, 错误的规则项", {
					"规则项": item,
				});
				continue
			}
		}
		if(checkPass){
			return msg||allErr? {res:true} : true
		}
		return msg||allErr? {res:false, msg:resMsg} : false
	}else{//or关联（只要有一项验证成功，则返回成功）
		for(let item of rules){
			if(isObject(item)){//对象，带参数
				if(!item.rule){
					//无规则跳过
					console.warn("function: regCheck, 警告，错误的规则项", {
						"规则项为空，已跳过": item,
					});
					continue
				}
				let checkObj = reg[item.rule](data, item.value);
				if(checkObj.res){
					return msg||allErr? {res:true} : true
				}else if(allErr){
					resMsg.push(checkObj.msg);
				}
			}else if(reg[item]){
				let checkObj = reg[item](data);
				if(checkObj.res){
					return msg||allErr? {res:true} : true
				}else if(allErr){
					resMsg.push(checkObj.msg);
				}
			}else{
				//错误的规则跳过，当做不存在
				console.warn("function: regCheck, 错误的规则项", {
					"规则项": item,
				});
				continue
			}
		}
		return msg||allErr? {res:false, msg:resMsg} : false
	}
}

/*
校验规则，不同规则能够匹配的数据类型并不一致，应予以注意
固定规则一般限定数据类型为字符串，含有必填校验。如需将规则与必填区分，应在规则外处理
不会对规则项的第二参数value进行内部合法校验
*/
const reg = {
	"required":function(data){
		let res = false,
			msg = "这是必填项";
		switch(typeAsk(data)){
			case "String":
				res = data.trim().length >0;
				break;
			case "Number":
				res = !isNaN(data);
				break;
			case "Array":
				res = data.length >0;
				break;
			case "Object":
				res = Object.keys(data).length >0;
				break;
			default:
				res = isValue(data)
		}
		return { res: res, msg: res?"":msg }
	},
	"number":function(data, value){//value长度验证，有效值：数组[1,6]
		let res = isNumber(data, true),
			msg = "请输入至少1位数字";
		if(isValue(value)&&!isArray(value)){
			res = false;
			msg = "未知错误";
			console.error("number校验规则参数出错(数组)：", value);
		}else if(res){
			let length = String(data).length,
				min, max;
			if(isArray(value)){
				min = value[0]||1;
				max = value[1]||Infinity;
				msg = "请输入" + (value[1]? (min+"至"+value[1]) : ("至少"+min));
				msg += "位数字";
				res = min <= length && length <= max;
			}else{
				res = length > 0
			}
		}
		return { res: res, msg: res?"":msg }
	},
	"string":function(data, value){//value长度验证，有效值：数组[1,6]
		let res = isStringOrNumber(data),
			msg = "请输入至少1个字符";
		if(isValue(value)&&!isArray(value)){
			res = false;
			msg = "未知错误";
			console.error("string校验规则参数出错(数组)：", value);
		}else if(res){
			let length = String(data).length,
				min, max;
			if(isArray(value)){
				min = value[0]||1;
				max = value[1]||Infinity;
				msg = "请输入" + (value[1]? (min+"至"+value[1]) : ("至少"+min));
				msg += "个字符";
				res = min <= length && length <= max;
			}else{
				res = length > 0
			}
		}
		return { res: res, msg: res?"":msg }
	},
	"int":function(data){
		let res = isStringOrNumber(data) && /^(0|[1-9][0-9]*|-[1-9][0-9]*)$/.test(String(data)),
			msg = "请输入整数";
		return { res: res, msg: res?"":msg }
	},
	"between":function(data, value){//value范围验证，有效值：数组[1,6]
		let res = isNumber(data, true),
			msg = "请输入有效数字";
		if(!isArray(value)){
			res = false;
			msg = "未知错误";
			console.error("between校验规则参数出错(数组)：", value);
		}else if(res){
			let number = Number(data),
				min = value[0] < value[1] ? value[0] : value[1],
				max = value[0] > value[1] ? value[0] : value[1],
				msg = `请输入介于${min}和${max}的数值`;
			res = min <= number && number <= max;
		}
		return { res: res, msg: res?"":msg }
	},
	"includes":function(data, value){//value匹配值验证，有效值：字符串abc
		let res = isStringOrNumber(data),
			msg;
		if(!isString(value)){
			res = false;
			msg = "未知错误";
			console.error("includes校验规则参数出错(字符串)：", value);
		}else{
			res = String(data).includes(value);
			msg = "未包含：" + value;
		}
		return { res: res, msg: res?"":msg }
	},
	"startWith":function(data, value){//value匹配值验证，有效值：字符串abc
		let res = isStringOrNumber(data),
			msg;
		if(!isString(value)){
			res = false;
			msg = "未知错误";
			console.error("startWith校验规则参数出错(字符串)：", value);
		}else{
			res = String(data).startsWith(value);
			msg = "未以" + value + "开始";
		}
		return { res: res, msg: res?"":msg }
	},
	"endWith":function(data, value){//value匹配值验证，有效值：字符串abc
		let res = isStringOrNumber(data),
			msg;
		if(!isString(value)){
			res = false;
			msg = "未知错误";
			console.error("endWith校验规则参数出错(字符串)：", value);
		}else{
			res = String(data).endsWith(value);
			msg = "未以" + value + "结束";
		}
		return { res: res, msg: res?"":msg }
	},
	"letter":function(data){
		let res = isStringOrNumber(data) && /^\w+$/.test(String(data)),
			msg = "请输入字母或数字";
		return { res: res, msg: res?"":msg }
	},
	"mobile":function(data){
		let res = isStringOrNumber(data) && /^1[3-9]\d{9}$/.test(String(data)),
			msg = "请输入正确的手机号";
		return { res: res, msg: res?"":msg }
	},
	"phone":function(data){
		let res = isStringOrNumber(data) && /^1[3-9]\d{9}$/.test(String(data)),
			msg = "请输入正确的手机号";
		return { res: res, msg: res?"":msg }
	},
	"password":function(data){
		let res = isString(data) && /^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d]{6,20}$/.test(data),
			msg = "请输入6到20位字母或数字，至少存在1个字母和1个数字";
		return { res: res, msg: res?"":msg }
	},
	"CN":function(data){
		let res = isString(data) && /^[\u4e00-\u9fa5]{1,}$/.test(data),
			msg = "请输入中文字符";
		return { res: res, msg: res?"":msg }
	},
	"zipcode":function(data){
		let res = isStringOrNumber(data) && /^[1-9][0-9]{5}$/.test(String(data)),
			msg = "请输入有效的6位邮编";
		return { res: res, msg: res?"":msg }
	},
	"email":function(data){
		let res = isString(data) && /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(data),
			msg = "请输入有效的邮件地址";
		return { res: res, msg: res?"":msg }
	},
	"chinaID":function(data){
		let res = isStringOrNumber(data) && ( /^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$/.test(String(data)) || /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(String(data)) ),
			msg = "请输入有效的身份证号";
		return { res: res, msg: res?"":msg }
	},
	"HKMpasser":function(data){
		let res = isStringOrNumber(data) && /^([A-Z]\d{6,10}(\(\w{1}\))?)$/.test(String(data)),
			msg = "请输入有效的港澳台通行证号";
		return { res: res, msg: res?"":msg }
	},
	"passport":function(data){
		let res = isStringOrNumber(data) && /^([a-zA-z]|[0-9]){5,17}$/.test(String(data)),
			msg = "请输入有效的护照号码";
		return { res: res, msg: res?"":msg }
	},
	"officerNumber":function(data){
		let res = isStringOrNumber(data) && /^[\u4E00-\u9FA5](字第)([0-9a-zA-Z]{4,8})(号?)$/.test(String(data)),
			msg = "请输入有效的军官证号码";
		return { res: res, msg: res?"":msg }
	},
	"date":function(data){
		let res = !/Invalid|NaN/.test(new Date(data).toString()),
			msg = "请输入有效的日期";
		return { res: res, msg: res?"":msg }
	},
	"money":function(data){
		let res = isStringOrNumber(data) && /^[0-9]+(.[0-9]{0,2})?$/.test(String(data)),
			msg = "请输入有效的金额";
		return { res: res, msg: res?"":msg }
	},
	"name":function(data){
		let res = isString(data) && /^[0-9]+(.[0-9]{0,2})?$/.test(data),
			msg = "请输入中文姓名";
		return { res: res, msg: res?"":msg }
	},
	
}

/*
文字高亮
options:{
	keyword:"关键词"
	color:"颜色值" (格式#fff或#ffffff或grb(255,255,255))
}
*/
const highlight = (str, options={}) => {
	if(!isString(str)){
		console.error("function: highlight, 参数类型错误", {
			"参数1(字符串)": typeAsk(str)
		});
		return str
	}
	let { keyword, color="red" } = options;
	if(!isValue(str)){
		console.error("function: highlight, str为空");
		return "";
	}
	let reg = new RegExp("(" + keyword + ")", "g");
	return isString(keyword) && isString(color) ? str.replace(reg, `<font style='color:${color};'>$1</font>`) : str
}

//字符串按匹配符切割成数组，contact连接符
const strToArr = (str, contact) => {
	if(!isString(str)){
		console.warn("function: strToArr, 参数类型错误", {
			"参数1(字符串)": typeAsk(str)
		});
		return []
	}
	if(!str){
		return []
	}
	if(isValue(contact)){
		return str.split(contact).filter(item=>{//非空值保留
			return isValue(item.trim())
		});
	}else{//下划线将会保留
		return str.match(/[\u4E00-\u9FA5\w]+/g)
	}
}

/*
抽取数组值组成字符串
options:{
	key:"对象键名"
	func:"自定义返回值函数"
	contact:"连接符" (默认",")
}
*/
const arrToStr = (arr, options={}) => {
	let { key="", func, contact="," } = options;
	if(!isArray(arr)){
		console.error("function: arrToStr, 参数类型错误", {
			"参数1(数组)": typeAsk(arr)
		});
		return ""
	}
	return arrTransform(arr, Object.assign(options, { format:"String" }) )
}

/*
抽取数组值或对象值组成新对象
options:{
	key:"对象键名"
	value:"自定义返回值函数"
}
*/
const arrToObj = (arr, options={}) => {
	if(!isArray(arr)){
		console.error("function: arrToObj, 参数类型错误", {
			"参数1(数组)": typeAsk(arr)
		});
		return {}
	}
	return arrTransform(arr, Object.assign(options, { format:"Object" }) )
}

/*
数组转换字符串、对象、新数组
options:{
	key:"对象键名" （转换为非对象时，表示结果值取自数组对象的对应属性值。转换为对象时，表示结果对象键名来自数组对象对应属性值，可为字符或自定义函数）
	value:"对象键值" （仅转换为对象时用，表示结果对象属性值来自数组对象对应属性值，可为字符或自定义函数）
	func:"自定义返回值函数" （除转换为对象以外类型使用，自定义结果值）
	contact:"连接符" (默认",") （仅转换为字符串时用，表示连接符）
	noRepeat:"不重复" （仅转换为数组时用，去除结果数组重复项）
	format:"返回类型"（默认Array数组，String字符串，Object对象）
}
*/
const arrTransform = (arr, options={}) => {
	let { key, value, func, contact=",", noRepeat="false", format="Array" } = options;
	if(!isArray(arr)){
		console.error("function: arrTransform, 参数类型错误", {
			"参数1(数组)": typeAsk(arr)
		});
		switch(format){
			case "String":
				return false
			case "Object":
				return {}
			case "Array":
				return []
		}
	}
	switch(format){
		case "String":
			return arr.map((item) => {
				if(isFunction(func)){
					return func(item);
				}else if(isValue(key)){
					return item[key]
				}else if(isValue(item) ){
					return item
				}
			}).filter((item) => {
				return isValue(item)
			}).join(contact)
		case "Object":
			let res = {};
			for(let i=0; i<arr.length; i++){
				let k, v;
				if(isValue(key)){//对象数组
					if(isFunction(key)){
						k = key(arr[i])
					}else{
						k = arr[i][key]
					}
					if(isFunction(value)){
						v = value(arr[i], i)
					}else{
						v = arr[i][value]
					}
				}else{//非对象数组
					k = arr[i];
					if(isFunction(value)){
						v = value(arr[i], i)
					}else{
						v = value
					}
				}
				res[k] = v
			}
			return res
		case "Array":
			let tempArr =[];
			for(let item of arr){
				if(isFunction(func)){
					tempArr.push(func(item));
				}else if(isValue(key)){
					tempArr.push(item[key]);
				}else if(isValue(item) ){
					tempArr.push(item);
				}
			}
			return noRepeat ? [...new Set(tempArr)] : tempArr
	}
}

/*
数组去重，一般数组或对象数组
options：
	字符串，单个对象键名，或拼接字符串键名
	数组，多个对象键名
	对象，对象包裹的对象键名
*/
const arrNoRepeat = (arr, options=[]) => {
	switch(typeAsk(options)){
		case "String":
			options = strToArr(options);
			break;
		case "Array":
			break;
		case "Object":
			if(isValue(options.key)){
				options = [options.key]
			}
			break;
	}
	if(!isArray(arr) || !isArray(options) ){
		console.error("function: arrNoRepeat, 参数类型错误", {
			"参数1(数组)": typeAsk(arr),
			"参数2(字符串/字符串数组)": typeAsk(options),
		});
		return arr
	}
	if(options.length){//如果存在选项，为对象去重
		let res = [];
		for(let item of arr){
			if(!arrFind(res, {
				func:function(obj){
					for(let key of options){
						if(obj[key] !== item[key]){
							return false
						}
					}
					return true
				}
			})){
				res.push(item)
			}
		}
		return res
	}else{
		return [...new Set(arr)]
	}
}

/*
检索目标数组，返回匹配
参数: targetArr,目标数组
options:{
	key:"对象键名"
	value:"匹配值"
	func:"自定义匹配函数" (存在则优先使用)
	format:"返回格式" (默认Boolean、index下标、item对象或元素本身)
	strict:"严格比较" (默认false)
}
*/
const arrFind = (targetArr, options={}) => {
	let { key, value="", func, format="Boolean", strict=false } = options;
	if(!isArray(targetArr)){
		console.error("function: arrFind, 参数类型错误", {
			"参数1(数组)": typeAsk(targetArr)
		});
		switch(format){
			case "index":
				return -1;
				break;
			case "item":
				return undefined;
			default:
				return -1;
		}
	}
	let index = -1;
	for(let i =0; i < targetArr.length; i++){
		if(isFunction(func)){
			if(func(targetArr[i])){
				index = i;
				break;
			}
		}else if(isValue(key)){
			if(isEqual(targetArr[i][key], value, strict)){
				index = i;
				break;
			}
		}else{
			if(isEqual(targetArr[i], value, strict)){
				index = i;
				break;
			}
		}
	}
	switch(format){
		case "index":
			return index;
			break;
		case "item":
			return index != -1 ? targetArr[index] : undefined;
		default:
			return index != -1;
	}
}

/*
查找目标数组中匹配的对象，返回对象数组，过滤器也为对象数组，对象包含待匹配的键名和键值，以及匹配规则类型
filters:[
	{
		func:"匹配类型",
		value:"匹配值",
		key:"待检查的键名",
		strict:"严格相等",
	},
	……
]
匹配类型func：
	value:"值匹配或多值匹配"
	max:"最大值" (匹配小于value,含等于)
	min:"最小值" (匹配大于value,含等于)
	maxNeq:"最大值" (匹配小于value)
	minNeq:"最小值" (匹配大于value)
	day:"匹配日期" (从今天开始,加上value的天数)
	month:"匹配年月" (从今天开始,加上value的月数)
	startDate:"开始日期" (匹配value之后的日期，含value)
	endDate:"结束日期" (匹配value之前的日期，含value)
	include:"包含字符串" (匹配含有value的目标字符串)
	startWith:"开始字符串" (匹配以value开始的目标字符串)
	endWith:"结束字符串" (匹配以value结束的目标字符串)
	function:"自定义函数" (函数返回Boolean)
*/

const arrFilt = (targetArr, filters=[]) => {
	if(isObject(filters)){
		filters = [filters]
	}
	if(!isArray(targetArr) || !isArray(filters)){
		console.error("function: arrFilt, 参数类型错误", {
			"参数1(数组)": typeAsk(targetArr),
			"参数2(数组/对象)": typeAsk(filters)
		});
		return targetArr;
	}
	return targetArr.filter((item) => {
		let check = true;//校验当前项是否通过所有过滤器规则
		for(let ft of filters){//循环所有过滤器
			let {key, func="value", value="", strict=false} = ft;//被筛选的键、筛选规则、匹配值、严格模式
			//校验过滤器，必须有判断类型，目标为Object和Array时须指定键名或使用自定义function
			const itemType = typeAsk(item);
			const isFunc = isFunction(func);
			if(!isValue(func) || ( (itemType == "Object" || itemType == "Array") && !isValue(key) && !isFunc) ){
				console.error("function: arrFilt, 过滤器参数错误，当前匹配失败：", {
					"目标": item,
					"过滤器": ft
				});
				check = false;
				break;
			}
			//校验目标
			let target;
			if(!isFunc && (itemType == "Object" || itemType == "Array") ){
				if(!item.hasOwnProperty(key)){//null和undefined相等
					console.warn(`function: arrFilt, 注意：过滤器检索目标不存在匹配键${key}，当前匹配失败`, ft);
					check = false;
					break;
				}
				target = item[key];
			}else{
				target = item;
			}
			
			const targetType = typeAsk(target);
			const valueType = typeAsk(value);
			let endLoop = false;
			//执行
			switch(func){
				case "value"://值匹配
				case "mulValue"://多值匹配。value可匹配null，undefined会被赋予空默认值当做全部
					if(value === ""){//空字符代表全部，跳过
						continue
					}else if(valueType == "Array"){//数组，多值匹配
						if( !arrFind(value, {type:"Array", value:target, strict:strict}) ){//未找到
							check = false;
							endLoop = true;
						}
					}else if(!isEqual(value, target, strict)){//值匹配
						//当前筛选器不匹配，跳出循环
						check = false;
						endLoop = true;
					}
					break;
				case "max"://最大值匹配，括号中为真表示通过
					if(!(Number(value) - Number(target) >= 0 )){
						check = false;
						endLoop = true;
					}
					break;
				case "min"://最小值匹配，括号中为真表示通过
					if(!(Number(target) - Number(value) >= 0 )){
						check = false;
						endLoop = true;
					}
					break;
				case "maxNeq"://最大值（不包含）匹配
					if(!(Number(value) - Number(target) > 0)){
						check = false;
						endLoop = true;
					}
					break;
				case "minNeq"://最小值（不包含）匹配
					if(!(Number(target) - Number(value) > 0)){
						check = false;
						endLoop = true;
					}
					break;
				case "day"://指定日期匹配，value值为从当天开始加减天数
					if(datePoint(target, {format:"YYYY-MM-DD"}) != datePoint("", {day:Number(value), format:"YYYY-MM-DD"}) ){
						check = false;
						endLoop = true;
					}
					break;
				case "month"://指定月份匹配，value值为从当天开始加减月数
					if(datePoint(target, {format:"YYYY-MM"}) != datePoint("", {month:Number(value), format:"YYYY-MM"}) ){
						check = false;
						endLoop = true;
					}
					break;
				case "startDate":
					if(new Date(target).getTime() < new Date(value).getTime()){
						check = false;
						endLoop = true;
					}
					break;
				case "endDate":
					if(new Date(target).getTime() > new Date(value).getTime()){
						check = false;
						endLoop = true;
					}
					break;
				case "include":
					if(targetType != "String" || valueType != "String"){
						console.error("function: arrFilt, 字符搜索功能，过滤器参数类型错误", {
							"参数1[n].xxx(字符串)": targetType,
							"参数2[n].value(字符串)": valueType
						});
						check = false;
						endLoop = true;
						break;
					}
					if(!target.includes(value)){
						check = false;
						endLoop = true;
					}
					break;
				case "startWith":
					if(targetType != "String" || valueType != "String"){
						console.error("function: arrFilt, 字符搜索功能，过滤器参数类型错误", {
							"参数1[n].xxx(字符串)": targetType,
							"参数2[n].value(字符串)": valueType
						});
						check = false;
						endLoop = true;
						break;
					}
					if(!target.startsWith(value)){
						check = false;
						endLoop = true;
					}
					break;
				case "endWith":
					if(targetType != "String" || valueType != "String"){
						console.error("function: arrFilt, 字符搜索功能，过滤器参数类型错误", {
							"参数1[n].xxx(字符串)": targetType,
							"参数2[n].value(字符串)": valueType
						});
						check = false;
						endLoop = true;
						break;
					}
					if(!target.endsWith(value)){
						check = false;
						endLoop = true;
					}
					break;
				default://无匹配类型，默认匹配失败
					if(!isFunc || (isFunc && !func(target))){
						check = false;
						endLoop = true;
					}
			}
			if(endLoop){
				break;
			}
		}
		//校验结果，true保留，false剔除
		return check
	});
}

/*
对象数组排序，返回新数组。
rules:[
	{
		key:"对象键名"
		func:"正倒序" (默认asc升序，desc降序或自定义排序函数)
	}
	……
]
*/
const arrSort = (arr, rules=[]) => {
	if(isObject(rules)){
		rules = [rules]
	}
	if(!isArray(arr) || !isArray(rules)){
		console.error("function: arrSort, 参数类型错误，排序失败", {
			"参数1(数组)": typeAsk(arr),
			"参数2(数组/对象)": typeAsk(rules)
		});
		return arr
	}
	let result = [...arr];
	if(!result.length){
		return result;
	}
	//多级排序，逐个排序器比较
	result.sort((a, b) => {
		for(let rule of rules){
			let {key, func} = rule;//升降序、键名、函数
			let tgA = isValue(key) ? a[key] : a;
			let tgB = isValue(key) ? b[key] : b;
			const typeA = typeAsk(tgA);
			const typeB = typeAsk(tgB);
			//存在比对函数，优先使用比对函数
			if(isFunction(func)){
				let compare = func(tgA, tgB);
				if(!compare || !isNumber(compare)){//空值、未定义、0或函数错误进入下一个排序
					continue
				}else{
					return compare
				}
			}else{
				//不符合比对类型或比对相等的，进入下一个排序
				if( (typeA != "String" && typeA != "Number" && typeA != "Date") || isEqual(tgA, tgB) ){
					continue
				}else{
					if(typeA == "Date"){
						return direction(tgA.getTime() - tgB.getTime(), func)
					}else{
						let nA = Number(tgA), nB = Number(tgB);
						if(!isNaN(nA) && !isNaN(nB)){
							return direction(nA - nB, func)
						}else{
							return direction(String(tgA) > String(tgB) ? 1:0, func);
						}
					}
				}
			}
		}
		//所有规则运行完毕，返回相等
		return 0
	})
	function direction(val, type){
		if(type == "asc"){
			return val>0 ? 1:-1
		}else if(type == "desc"){
			return val>0 ? -1:1
		}
	}
	return result
}

//数组删除元素
/*
options:{
	key:"对象键名"
	value:"匹配值"
	func:"自定义匹配函数" (存在则优先使用)
}
*/
const arrDel = (arr, options={})=>{
	let {key, value, func} = options;
	if(!isArray(arr)||!isValue(value)){
		console.error("function: arrDel, 参数错误", {
			"参数1(数组)": typeAsk(arr),
			"参数2.value(字符串/必须)": value
		});
		return arr
	}
	for(let i=arr.length-1; i>=0; i--){
		let item = arr[i],
			check = false;
		if(isFunction(func)){
			check = func(item)
		}else if(key){
			check = item[key] == value
		}else{
			check = item == value
		}
		if(check){
			arr.splice(i, 1);
		}
	}
	return [...arr]
}

//数组求和
/*
options:{
	key:"对象键名"
	func:"自定义求和函数" (存在则优先使用)
}
*/
const arrSum = (arr, options={})=>{
	let {key, func} = options;
	let res =0;
	if(!isArray(arr)){
		console.error("function: arrSum, 参数错误", {
			"参数1(数组)": typeAsk(arr)
		});
		return res
	}
	if(isFunction(func)){
		for(let item of arr){
			res += func(item)
		}
	}else if(key){
		for(let item of arr){
			res += Number(item[key])
		}
	}else{
		for(let item of arr){
			res += Number(item)
		}
	}
	return res
}

/*
深拷贝对象，解决循环引用，栈溢出，保持引用关系。数据量过大时浏览器异常或宕机，且无提示
options:{
	type:"对象属性类型" (默认normal，可枚举类型，不含继承，不含Symbol; andSymbol，包含Symbol的可枚举类型; all，所有类型，不含继承。包括可枚举、不可枚举、Symbol,一般会包含系统属性)
	loopCheck:"引用关系检测" (默认false)
}
引用关系检测会极大降低运行效率，normal、Symbol模式下默认关闭，all模式下默认开启
*/
const clone = (obj, options={}) => {
	let {type="normal", loopCheck = false} = options;
	if(type == "all" && !options.hasOwnProperty(loopCheck)){
		loopCheck = true
	}
	const repeatList = [];
	const loopList = [];
	let result = _dataSwitch(obj);
	while(loopList.length) {
		const node = loopList.pop();
		const parent = node.parent;
		const data = node.data;
		const dataType = typeAsk(data);
		//关键：parent与data类型一致
		if(dataType == "Object" || dataType == "Array"){
			let keys;
			if(type == "normal"){
				keys = Object.keys(data);
			}else if(type == "andSymbol"){
				keys = [...Object.keys(data), ...Object.getOwnPropertySymbols(data)];
			}else if(type == "all"){
				keys = Reflect.ownKeys(data);
			}
			for(let key of keys){
				parent[key] = _dataSwitch(data[key])
			}
		}else if(dataType == "Set" || dataType == "WeakSet"){
			for(let item of data.keys()){
				parent.add(_dataSwitch(item))
			}
		}else if(dataType == "Map" || dataType == "WeakMap"){
			for (let [key, value] of data.entries()) {
				parent.set(_dataSwitch(key), _dataSwitch(value));
			}
		}
	}
	return result;
	
	function _dataSwitch(data){
		let res;
		switch(typeAsk(data)){
			case "Object":
				res = {};
				break;
			case "Array":
				res = [];
				break;
			case "Date":
				return new Date(data);
			case "Function":
				return new Function('return ' + data.toString())();
			case "Set":
				res = new Set();
				break;
			case "WeakSet":
				res = new WeakSet();
				break;
			case "Map":
				res = new Map();
				break;
			case "WeakMap":
				res = new WeakMap();
				break;
			default:
				return data
		}
		if(loopCheck){//要求处理引用关系
			const repeat = _find(data);
			if(repeat){
				return repeat.target;
			}else{
				repeatList.push({
					source: data,
					target: res
				})
			}
		}
		loopList.push({
			parent: res,
			data: data,
		});
		return res;
	}
	function _find(data){
		for(let item of repeatList){
			if(item.source === data){
				return item;
			}
		}
		return false
	}
}

//多对象合并属性到第一个对象
//使用arguments必须用fanction声明函数
const extend = function() {
	let arr = [];
	for(let i=0; i<arguments.length; i++){
		if(i==0){
			arr.push(arguments[i])
		}
		arr.push(cloneWith(arguments[i]))
	}
	return Object.assign(...arr)
}

//对象重建，深拷贝对象且通过处理函数仅保留需要的对象属性。不进行循环校验
//callback:
//	res: 结果对象
//	data: 目标对象（被拷贝对象）
//	level: 当前所处层级（如果父级为数组，层级不会增加）
//	keyName: 当前对象所在的父级键名(如果父级为数组，则向上寻找)
//如果要在结果对象保留目标对象的属性，res[键名]=data[键名]即可。
//如果保留属性类型为对象或数组，res[键名]=""即可，会自动向下遍历，增加层级
//如果目标对象的数组或对象属性未在结果对象中保留，则终止向下遍历
const rebuild = (obj, callback) => {
	if(!isFunction(callback)){
		console.error("function: rebuild, 参数错误", {
			"参数2(函数)": typeAsk(callback)
		});
		return obj
	}
	const loopList = [];
	let result = _dataSwitch(obj,0,null);
	while(loopList.length) {
		const node = loopList.pop();
		let {res, data, level, keyName} = node;
		const dataType = typeAsk(data);
		//关键：res与data类型一致
		if(dataType == "Object"){
			callback(node);
			let keys = Object.keys(data);
			for(let key of keys){
				if(isObjectOrArray(data[key])&&res.hasOwnProperty(key)){
					res[key] = _dataSwitch(data[key],level+1,key)
				}
			}
		}else if(dataType == "Array"){
			for(let item of data){
				res.push(_dataSwitch(item,level,keyName))
			}
		}
	}
	return result;
	
	function _dataSwitch(data,level,keyName){
		let res;
		switch(typeAsk(data)){
			case "Object":
				res = {};
				break;
			case "Array":
				res = [];
				break;
			default:
				return clone(data)
		}
		loopList.push({
			res,
			data,
			level,
			keyName
		});
		return res;
	}
}

/*
对象序列化为指定断句的字符串
options:{
	link:"属性值连接符"（默认:）
	stop:"属性隔断符"（默认;）
}
*/
const objToStr = (obj, options={}) => {
	let {link=":", stop=";"} = {};
	let res = "";
	let keys = Object.keys(obj);
	for(let key of keys){
		res += key + link + obj[key] + stop;
	}
	return res
}

/*
检索对象键值，返回匹配值在对象中是否存在或所在键名
options:{
	value:"匹配值"
	func:"自定义比较函数"
	format:"返回格式" (默认Boolean、key键名)
	strict:"严格比较" (默认false)
}
*/
const objFind = (targetObj, options={}) => {
	let { value, func, format="key", strict=false } = options;
	let targetType = typeAsk(targetObj);
	if(targetType != "Object" && targetType != "Array"){
		console.error("function: objFind, 参数类型错误", {
			"参数1(对象或数组)": targetType
		});
		return false
	}
	let key;
	let ks = Object.keys(targetObj);
	for(let k of ks){
		if(isFunction(func)){
			if(func(targetObj[k])){
				key = k;
				break;
			}
		}else{
			if(isEqual(targetObj[k], value, strict)){
				key = k;
				break;
			}
		};
	}
	if(format == "key"){
		return key;
	}else{
		return !!key;
	}
}

//对象合并，以第一个参数为目标，第一对象的属性被后续对象同名属性覆盖，浅拷贝
//使用arguments必须用fanction声明函数
const objMerge = function() {
	let res = arguments[0] ;
	if(!isObject(res)){
		console.error("function: objMerge, 参数类型错误", {
			"参数1(对象)": typeAsk(res)
		});
		return res
	}
	for(let i=1; i<arguments.length; i++){
		let obj = arguments[i];
		let force;
		if(!isObject(obj)){//非对象跳过
			continue
		}
		if(!Object.keys(res).length){//初对象无键值，则拷贝后续第一个有键值对象
			force=true
		}
		let keys = Object.keys(obj);
		for(let key of keys){
			if(res.hasOwnProperty(key) || force){
				res[key] = obj[key]
			}
		}
	}
	return res
}

/*
对比两个对象属性是否相等，用于请求对象数据的比对
options:{
	type:"对象属性类型" (默认normal，可枚举类型，不含继承，不含Symbol; andSymbol，包含Symbol的可枚举类型; all，所有类型，不含继承。包括可枚举、不可枚举、Symbol,一般会包含系统属性)
	strict:"严格模式" (遇到不可比对类型的属性一律判定不等，如对象、函数)
}
*/
const objCompare = (obj1, obj2, options={}) => {
	if(!isObject(obj1) || !isObject(obj2)){
		console.error("function: objCompare, 参数类型错误", {
			"参数1(对象)": typeAsk(obj1),
			"参数2(对象)": typeAsk(obj2)
		});
		return false
	}
	let {type="normal", strict=false} = options;
	let keys1, keys2;
	let compareType = ["String", "Number", "Boolean"];
	let res = true;
	if(type == "normal"){
		keys1 = Object.keys(obj1);
		keys2 = Object.keys(obj2);
	}else if(type == "andSymbol"){
		keys1 = [...Object.keys(obj1), ...Object.getOwnPropertySymbols(obj1)];
		keys2 = [...Object.keys(obj2), ...Object.getOwnPropertySymbols(obj2)];
	}else if(type == "all"){
		keys1 = Reflect.ownKeys(obj1);
		keys2 = Reflect.ownKeys(obj2);
	}
	if(!keys1 || !keys2 || keys1.length != keys2.length){
		return false
	}
	for(let key of keys1) {
		let type1 = typeAsk(obj1[key]);
		let type2 = typeAsk(obj2[key]);
		if(compareType.includes(type1) && compareType.includes(type2)){
			if(obj1[key] !== obj2[key]){
				res = false;
				break;
			}
		}else if(type1 == "Date" && type2 == "Date"){
			if(obj1[key].getTime() != obj2[key].getTime()){
				res = false;
				break;
			}
		}else if(compareType.includes(type1) || compareType.includes(type2)){//其中一项可对比，另一项不可对比
			res = false;
			break;
		}else{//均为不可对比的对象
			if(!strict){
				res = false;
				break;
			}
		}
	}
	return res
}

//使用路径访问对象深层属性，避免空值错误中断。可为对象和数组混合，建议3层以上时使用
const objPathGet = (obj, path) => {
	if( (!isObject(obj) && !isArray(obj)) || !isString(path)){
		console.error("function: objPathGet, 参数类型错误", {
			"参数1(对象/数组)": typeAsk(obj),
			"参数2(字符串)": typeAsk(path)
		});
		return null
	}
	let pathArr = strToArr(path, '.');
	if(pathArr.length){
		let tempObj = obj;
		for(let item of pathArr){
			if(!isValue(tempObj[item])){
				return null
			}
			tempObj = tempObj[item]
		}
		return tempObj
	}else{
		return obj
	}
}

//使用路径写入对象属性
const objPathSet = (obj, path, data, force=false) => {
	if( (!isObject(obj) && !isArray(obj)) || !isString(path)){
		console.error("function: objPathSet, 参数类型错误", {
			"参数1(对象/数组)": typeAsk(obj),
			"参数2(字符串)": typeAsk(path)
		});
		return null
	}
	let pathArr = strToArr(path, '.');
	if(pathArr.length){
		let tempObj = obj;
		for(let i=0,len=pathArr.length; i<len; i++){
			let item = pathArr[i];
			if(i!=len-1){
				if(isValue(tempObj[item])){
					tempObj = tempObj[item]
				}else if(force){//路径节点不存在，强制写入
					tempObj[item] ={};
					tempObj = tempObj[item];
				}else{//路径节点不存在且不强制写入，无操作跳出
					return
				}
			}else{//抵达目标节点
				tempObj[item] =data
			}
		}
	}
}

/*
给对象设置默认值，仅在对象没有属性时添加，不覆盖原有属性
data、options可以是对象数组，或单个对象
options:[{
	键名:默认值
}]
*/
const objDefValue = (data, options=[]) => {
	let res = data;
	if(isObject(data)){
		data = [data]
	}
	if(isObject(options)){
		options = [options]
	}
	if(!isArray(options)||!isArray(data)){
		console.error("function: objDefValue, 参数类型错误", {
			"参数1(对象/对象数组)": typeAsk(data),
			"参数2(对象/对象数组)": typeAsk(options),
		});
		return false
	}
	for(let item of data){
		for(let opt of options){
			let keys = Object.keys(opt);
			for(let key of keys){
				if(isObject(item) && !item.hasOwnProperty(key)){
					item[key] = opt[key]
				}
			}
		}
	}
	return res
}

//把html字符串中的常用字符实体转义
const htmlEncode = (html) => {
	if(!isString(html)){
		console.error("function: htmlEncode, 参数类型错误", {
			"参数1(字符串)": typeAsk(html)
		});
		return ""
	}
	if(!html.length){ return "" }
	let dic = {
		"&": "&amp;",
		"<": "&lt;",
		">": "&gt;",
		" ": "&nbsp;",
		"'": "&#39;",
		"\"": "&quot;"
	}
	return html.replace(/(&|<|>|[ ]|'|")/ig, function(all, key) {
		return dic[key];
	});
}

//把html字符串中的常用字符实体反转义
const htmlDecode = (html) => {
	if(!isString(html)){
		console.error("function: htmlDecode, 参数类型错误", {
			"参数1(字符串)": typeAsk(html)
		});
		return ""
	}
	if(!html.length){ return "" }
	let dic = {
		"amp": "&",
		"lt": "<",
		"gt": ">",
		"nbsp": " ",
		"#39": "'",
		"quot": "\""
	}
	return html.replace(/&(lt|gt|nbsp|amp|quot|#39);/ig, function(all, key) {
		return dic[key];
	});
}

//url提取参数
const urlParams = (url, noDecode=false) => {
	if(!isString(url)){
		console.error("function: urlParams, 参数类型错误", {
			"参数1(字符串)": typeAsk(url)
		});
		return {}
	}
	if(!noDecode){
		url = decodeURI(url);
	}
	// 编译为ios应用后，正则的前置断言写法引发未知错误导致页面不渲染，视情况更换算法
	// let res = {};
	// let paramStrArr;
	// if(url.includes("?")){//完整url
	// 	paramStrArr = regCheck(url, /(?<=[?&])[^?&]+=[^?&#]+/g);
	// }else{//参数部分
	// 	paramStrArr = regCheck(url, /[^&]+=[^&#]+/g);
	// }
	// for(let item of paramStrArr){
	// 	let split = item.split("=");
	// 	res[split[0]] = split[1];
	// }
	// return res
	//
	let res = {};
	if(url.includes("?")){//完整url
		url = url.substring(url.indexOf("?") + 1);
	}
	let paramStrArr = url.split("&");
	for(let item of paramStrArr) {
		if(item.length&&item.includes("=")){
			let parts = item.split("=");
			res[parts[0]] = parts[1];
		}
	}
	return res
}

//url参数对象转字符串
const stringParams = (params, url) => {
	let res="";
	if(!isObject(params) || (url && !isString(url))){
		console.error("function: stringParams, 参数类型错误", {
			"参数1(对象)": typeAsk(params),
			"参数2(字符串)": typeAsk(url)
		});
		return ""
	}
	for(let key of Object.keys(params)){
		if(res){
			res += "&"
		}
		res += `${key}=${isObject(params[key])?encodeURIComponent(JSON.stringify(params[key])):(isValue(params[key])?params[key]:"")}`
	}
	if(url){
		url += url.includes("?") ? "&" + res : "?" + res;
		return url
	}
	return res
}

/*
隐藏文字，用指定字符替换
options:{
	start:"替换字符的起始位置"
	max:"返回结果的最大字符数"
	min:"返回结果的最小字符数",
	replace:"替换符" （默认*）
}
*/
const hideWords = (text, options={}) => {
	let {start=0, max, min, replace="*"} = options;
	let arr;
	text = isValue(text)? String(text) :"";
	if(max){
		arr = text.split("", max);
	}else{
		arr = text.split("");
	}
	if(min && arr.length<min){
		if(arr.length < start){
			start = arr.length
		}
		arr.length = min
	}
	for(let i=start; i<arr.length; i++){
		arr[i] = replace;
	}
	return arr.join("")
}

//颜色值转换，默认返回值数组
const colorConvert = (data, format="HEX") => {
	const type = typeAsk(data);
	let colorArr;
	let err = false;
	//先全部转换成数值数组
	if(type == "Array" && data.length == 3){
		colorArr = [...data];
	}else if(type == "String"){
		if(data.startsWith("#")){
			if(data.length == 4){
				colorArr = [
					parseInt(data.substring(1,2) + data.substring(1,2), 16),
					parseInt(data.substring(2,3) + data.substring(2,3), 16),
					parseInt(data.substring(3,4) + data.substring(3,4), 16),
				]
			}else if(data.length == 7){
				colorArr = [
					parseInt(data.substring(1,3), 16),
					parseInt(data.substring(3,5), 16),
					parseInt(data.substring(5,7), 16),
				]
			}else{
				err = true
			}
		}else{
			colorArr = data.match(/\d+/g);
			if(colorArr.length != 3 && colorArr.length != 4){
				err = true
			}
		}
	}
	for(let i=0; i<colorArr.length; i++){
		colorArr[i] = parseInt(colorArr[i]);
		if(colorArr[i] < 0){ colorArr[i] = 0 }
		if(colorArr[i] > 255){ colorArr[i] = 255 }
	}
	if(err){
		console.error("function: colorConvert, 参数错误, 不是有效的颜色值",{
			"参数1": data
		})
	}
	colorArr.length = 3;
	switch(format){
		case "HEX":
			let str = "#";
			str += colorArr[0] <16 ? "0"+ colorArr[0].toString(16) : colorArr[0].toString(16);
			str += colorArr[1] <16 ? "0"+ colorArr[1].toString(16) : colorArr[1].toString(16);
			str += colorArr[2] <16 ? "0"+ colorArr[2].toString(16) : colorArr[2].toString(16);
			return str
		case "rgb":
			return "rgb("+ colorArr.join() +")"
		case "rgba":
			return "rgb("+ colorArr.join() +",1)"
		default:
			return colorArr
	}
}

//字符串拆分数值和单位
const splitUnit = (data) => {
	let str = String(data);
	let unit = str.match(/[a-z|A-Z|%]+$/);
	let val = str.replace(unit, "");
	if(!isNumber(val, true)){
		console.error("function: splitUnit, 参数错误，只能转换一个数值与一个单位",{
			"参数1": data
		})
		return []
	}
	return [Number(val), unit?unit[0]:""]
}

//数字修正小数位，按末位小数四舍五入
const numberFix = (data, num=0) =>{
	if(!isNumber(data, true)){
		console.error("function: numberFix, 参数类型错误",{
			"参数1(有效数字)": typeAsk(data)
		})
	}
	if(!isNumber(num, true)){
		return data
	}
	let multiplier = Math.pow(10, num);
	return Math.round(data * multiplier) / multiplier
}

/*
树遍历，无返回
func传入参数：
{
	node:"节点"
	level:"层级"（从0开始）
	parent:"父节点引用"
	first:"同级中是否为第一个节点"（只有true值有实际意义）
	last:"同级中是否为最后一个节点"（只有true值有实际意义）
	type:"节点类型"（root根，node树枝，leaf叶子）
}

options:{
	key:"子节点键名"（默认children）
}
*/
const treeMap = (data, func, options={}) => {
	if(!isFunction(func) || !isObjectOrArray(data)){
		console.error("function: treeMap, 参数类型错误",{
			"参数1(对象/数组)": typeAsk(data),
			"参数2(函数)": typeAsk(func),
		})
	}
	let {key="children"} = options;
	const loopList = [{
		data: data,
		level: 0
	}];
	while(loopList.length) {
		const obj = loopList.pop();
		let node = obj.data,
			level = obj.level,
			parent = obj.parent,
			first = obj.first,
			last = obj.last,
			type = typeAsk(node);
		if(type=="Array"){
			for(let i=node.length-1; i>=0; i--){
				loopList.push({
					data: node[i],
					level: level,
					parent: parent,
					first: i==0,
					last: i==node.length-1
				})
			}
		}else if(type=="Object"){
			let type;
			let typeOf = typeAsk(node[key]);
			if(typeOf=="Object"||(typeOf=="Array"&&node[key].length)){
				type = "node";
			}else{
				type = "leaf"
			}
			if(level==0){
				type = "root"
			}
			let res = func({
				node,
				level,
				parent,
				first,
				last,
				type
			});
			if(res=="return"){
				return
			}
			if(node[key]){
				loopList.push({
					data: node[key],
					level: level +1,
					parent: node,
					first: true,
					last: true
				})
			}
		}
	}
}

//从身份证获取年龄
const idCardGetAge = (idCard) => {
	let len = (idCard + "").length;
	let strBirthday = "";
	if(len == 18){
		//处理18位的身份证号码从号码中得到生日和性别代码
		strBirthday = `${idCard.substr(6, 4)}/${idCard.substr(10, 2)}/${idCard.substr(12, 2)}`
	}
	if(len == 15){
		let birthdayValue = "";
		birthdayValue = idCard.charAt(6) + idCard.charAt(7);
		if(parseInt(birthdayValue) < 10){
			strBirthday = `20${idCard.substr(6, 2)}/${idCard.substr(8, 2)}/${idCard.substr(10, 2)}`
		}else{
			strBirthday = `19${idCard.substr(6, 2)}/${idCard.substr(8, 2)}/${idCard.substr(10, 2)}`
		}
	}
	let birthDate = new Date(strBirthday);
	let nowDateTime = new Date();
	let age = nowDateTime.getFullYear() - birthDate.getFullYear();
	//再考虑月、天的因素;.getMonth()获取的是从0开始的，这里进行比较，不需要加1
	if( nowDateTime.getMonth() < birthDate.getMonth() ||
		(nowDateTime.getMonth() == birthDate.getMonth() && nowDateTime.getDate() < birthDate.getDate())
	){
		age--;
	}
	return age;
 }
 
 //字符串截取
 /* options:
		正数，取开始指定数量字符，往后舍去
		负数，取末尾开始向前指定数量字符，往前舍去
		数组，取指定区间字符
 */
 const textCut = (data, options) => {
	if(!isString(data)){
		console.error("function: textCut, 参数类型错误",{
			"参数1(字符串)": typeAsk(data)
		})
	}
	if(isArray(options)){
		return data.substring(...options)
	}else if(options<0){
		let len = data.length;
		return data.substring(len + options, len)
	}else if(options>0){
		return data.substring(0,options)
	}
	return data
 }
 
 //首字母大写
 const firstUpper = (data) => {
	let res = String(data);
	return res.charAt(0).toUpperCase() + res.substring(1)
 }

//向calc字符串插入值，以第一个和最后一个括号最大匹配
const setCalc = (data, value) => {
	let res = data.match(/calc\((.+)\)/);
	if(res&&res.length==2){
		let oldStr = res[1],
			newStr = oldStr;
		if(value){
			value = String(value).trim();
			newStr += value.startsWith("-")? " "+ value :" + "+ value;
			return data.replace(oldStr, newStr)
		}
	}else{
		console.error("function: setCalc, 参数错误",{
			"参数1(字符串)": data,
			"参数2(字符串)": value,
		})
	}
	return data
}

//取出calc字符串中的值，以第一个和最后一个括号最大匹配
const getCalc = (data) => {
	let res = data.match(/calc\((.+)\)/);
	return res? res[1] :""
}

/*
uniapp功能
*/
const msgDuration = 2000;

//uniapp消息。check，验证。get，获取信息。act，数据处理。
//集成自定义notify通知组件，当options为字符串时表示使用通知组件。tempMask通知组件开启临时遮罩屏蔽屏幕点击
const msg = (title, options={}, tempMask) => {
	if(!isValue(title)){
		return;
	}
	if(options&&isString(options)){//使用notify组件，查找当前页面是否存在notify组件，存在则使用
		let pages = getCurrentPages();
		let currPage = pages[pages.length -1];
		let notify = objPathGet(currPage, "$vm.$refs.vi_notify");
		if(notify){
			notify.open(title, options, tempMask);
			return
		}
		options ={}
	}
	let { duration = msgDuration, mask = false, icon = 'none', position = 'center', done } = options;
	uni.showToast({
		title,
		duration,
		mask,
		icon,
		position,
	});
	//showToast内的success、complete回调都会立即执行
	if(isFunction(done)){
		setTimeout(()=>{
			done()
		}, duration)
	}
}

const modal = (title, content, options={}) => {
	return new Promise((resolve, reject) => {
		uni.showModal({
			title: title ||"",
			content: content ||"",
			success: (res) => {
				if (res.confirm) {
					resolve();
				} else if (res.cancel) {
					reject();
				}
			},
			fail: () => {
				reject();
			}
		});
	})
}

//uniapp链接封装
const linkTo = (url, type = "nomal") => {
	if(!url){
		console.error("function: linkTo, 参数错误", {
			"参数1(字符串/不为空)": url
		});
		return false
	}
	if(url=="back"){
		goback()
	}
	switch(type){
		case "nomal":
			uni.navigateTo({
				url:url
			})
			break;
		case "forward":
			uni.redirectTo({
				url:url
			})
			break;
		case "alone":
			uni.reLaunch({
				url:url
			})
			break;
		case "tab":
			uni.switchTab({
				url:url
			});
			break;
		case "outside":
			location.href = url.includes("http")? url: "http://" + url;
			break;
		case "back"://返回至路径为url的页面，多层跳回。无匹配则抛出false
			goback();
			break;
	}
}

const tips = (text, len, options) => {
	if(!len || lengthOf(text, {CN2: true}) > len*2){
		msg(text, options);
	}
}

/*
子组件查找，返回匹配的组件列表
_this前组件对象
name组件的配置name
keys其他匹配属性名（字符串或字符串数组）
*/
const getChildren = (_this, name, keys=[]) => {
	if(isString(keys)){
		keys = [keys]
	}
	if(!name || !isString(name) || !isArray(keys)){
		console.error("function: getChildren, 参数错误", {
			"参数2(字符串/不为空)": name,
			"参数3(字符串/数组)": typeAsk(keys)
		});
	}
	let res = [];
	let nextTask = isArray(_this.$children) ? [_this.$children] :[];
	while (nextTask.length) {
		let childrenArr = nextTask.pop();
		for(let item of childrenArr){
			if(item.$options.name == name){
				//匹配其他属性
				let check=true;
				for(let key of keys){
					if(!isValue(key)){
						console.error("function: getChildren, 参数错误", {
							"参数3": keys
						});
						return []
					}
					if(item[key] != _this[key]){
						check = false;
						break;
					}
				}
				if(check){
					res.push(item)
				}
			}else if(isArray(item.$children)){
				nextTask.push(item.$children)
			}
		}
	}
	return res
}

/*
父组件查找
_this前组件对象
name组件的配置name
keys其他匹配属性名（字符串或字符串数组）
*/
const getParent = (_this, name, keys=[]) => {
	if(isString(keys)){
		keys = [keys]
	}
	if(!name || !isString(name) || !isArray(keys)){
		console.error("function: getParent, 参数错误", {
			"参数2(字符串/不为空)": name,
			"参数3(字符串/数组)": typeAsk(keys)
		});
	}
	let parent = _this.$parent;
	let parentName = parent.$options.name;
	let keyCheck = true;
	//进行第一次检测
	for(let key of keys){
		if(!isValue(key)){
			continue
		}
		if(_this[key] != parent[key]){
			keyCheck = false;
			break;
		}
	}
	while(parentName !== name || !keyCheck) {
		parent = parent.$parent;
		if (!parent) return undefined;
		parentName = parent.$options.name;
		keyCheck = true;
		for(let key of keys){
			if(!isValue(key)){
				continue
			}
			if(_this[key] != parent[key]){
				keyCheck = false;
				break;
			}
		}
	}
	return parent;
}

//返回上一页
const goback = () => {
	let arr = getCurrentPages();
	if (arr.length == 1) {
		//当返回的页面栈为1时，默认返回首页
		uni.reLaunch({
			url:'/pages/index/index'
		})
	}else{
		uni.navigateBack();
	}
}

//运行页面栈某处的函数
const callPage = (funcName, num=-1, params) => {
	if(!funcName||!isString(funcName)||!isNumber(num)||num>0){
		console.error("function: refresh, 参数类型错误", {
			"参数1(字符串/不为空)": funcName,
			"参数2(数字/小于等于0)": typeAsk(num),
		});
		return
	}
	let pages = getCurrentPages();
	let beforePage = pages[pages.length -1 + num];
	if(beforePage && isFunction(beforePage.$vm[funcName])){
		beforePage.$vm[funcName](params)
	}
}

export default{
	//基础类
	isValue,//非空变量判断
	isDefined,//已定义变量判断
	isNonzero,//非零有效值判断
	typeAsk,//获取数据类型
	isString,//判断类型为字符串
	isNumber,//判断类型为数值
	isStringOrNumber,//判断类型为字符串或数值
	isBoolean,//判断类型为布尔
	isDate,//判断类型为日期
	isSymbol,//判断类型为Symbol 
	isNull,//判断类型为null
	isUndefined,//判断类型为undefined
	isObject,//判断类型为对象
	isArray,//判断类型为数组
	isFunction,//判断类型为函数
	isObjectOrArray,//判断类型为对象或数组
	isEmpty,//判断数据为空对象、空数组或空值
	isColor,//判断字符串为颜色串，不进行合法值校验
	isEqual,//相等比较
	lengthOf,//获取数据长
	throttle,//节流防抖
	locker,//锁柜
	//日期类
	dateFormat,//日期格式化
	datePoint,//日期点
	dateDiffer,//日期差值
	timeFormat,//时间格式化
	timeDiffer,//时间差值
	//数据类
	reg,//预定义正则表达式执行
	regCheck,//可使用多重预定义正则表达式的，数据校验
	highlight,//字符串高亮
	strToArr,//字符串分割成数组
	arrToStr,//数组、对象数组提取成字符串
	arrToObj,//数组、对象数组提取成对象
	arrTransform,//对象、对象数组转换字符串、对象、新数组
	arrNoRepeat,//数组，去重
	arrFind,//数组查找
	arrFilt,//数组过滤
	arrSort,//数组多级排序
	arrDel,//数组删除元素
	arrSum,//数组加和
	clone,//深拷贝单个对象
	extend,//深拷贝多个对象，行为同Object.assign
	rebuild,//对象重建
	objToStr,//对象序列化为指定断句的字符串
	objFind,//对象查找
	objMerge,//对象合并
	objCompare,//比较两个对象各属性值是否相等
	objPathGet,//对象深层路径访问
	objPathSet,//对象深层路径写入
	objDefValue,//设置对象默认值
	htmlEncode,//html字符实体转义，空格->&nbsp;
	htmlDecode,//html字符实体反转义，&nbsp;->空格
	urlParams,//url提取参数
	stringParams,//url参数对象转字符串
	hideWords,//隐藏部分文字，用*显示
	colorConvert,//颜色值格式转换，16进制、rgb、数组
	splitUnit,//字符串拆分数值和单位
	numberFix,//数字修正小数位
	treeMap,//树遍历
	//业务类
	idCardGetAge,//身份证提取年龄
	textCut,//字符串截取
	firstUpper,//首字母大写
	setCalc,//向calc字符串插入值
	getCalc,//取出calc字符串中的值
	//uniapp
	msgDuration,//消息通知默认持续时间
	msg,//消息通知
	modal,//确认窗，promise化
	linkTo,//链接跳转
	tips,//超过字数时显示提示
	getChildren,//子组件查找
	getParent,//父组件查找
	goback,//返回上一页面
	callPage,//运行页面栈往前某层（之前某页）的函数
}